package gicom;

import gicom.config.GenericConfig;
import gicom.config.ORBConfig;
import gicom.config.ObjectConfig;
import gicom.config.POAConfig;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;
import org.omg.PortableServer.POAManagerPackage.AdapterInactive;
import org.omg.PortableServer.POAPackage.AdapterAlreadyExists;
import org.omg.PortableServer.Servant;

public class GenericManager {

    private class ManagedObject<X extends GenericConfig, Y> {

        public X config;
        public Y object;

        public ManagedObject(X config) {
            this.config = config;
        }
    }

    private Map<String, ManagedObject<ORBConfig, ORB>> orbs;
    private Map<String, ManagedObject<POAConfig, POA>> poas;
    private Map<String, ManagedObject<ObjectConfig, GenericObject>> objects;

    public GenericManager() throws Exception {
        this.orbs = new HashMap<String, ManagedObject<ORBConfig, ORB>>();
        this.poas = new HashMap<String, ManagedObject<POAConfig, POA>>();
        this.objects = new HashMap<String, ManagedObject<ObjectConfig, GenericObject>>();
    }

    public synchronized boolean orbExists(String id) {
        if (orbs.containsKey(id)) return true;
        return false;
    }
    
    public synchronized boolean poaExists(String id) {
        if (poas.containsKey(id)) return true;
        return false;
    }
    
    public synchronized boolean objectExists(String id) {
        if (objects.containsKey(id)) return true;
        return false;
    }
    
    public synchronized boolean idExists(String id) {
        if (orbs.containsKey(id)) return true;
        if (poas.containsKey(id)) return true;
        if (objects.containsKey(id)) return true;
        return false;
    }

    public synchronized void createORB(ORBConfig config) throws Exception {
        try {
            if (idExists(config.getId())) {
                throw new Exception("This id already exists.");
            }
            // Put the orb in the orb list
            ManagedObject<ORBConfig, ORB> managedORB = new ManagedObject<ORBConfig, ORB>(config);
            orbs.put(config.getId(), managedORB);
            // Load the ORB
            System.out.println("ORB " + managedORB.config.getId() + " loading");
            // Create the ORB
            managedORB.object = ORB.init(new String[0], managedORB.config.getProperties());
            // Activate the RootPOA
            POA rootPOA = POAHelper.narrow(managedORB.object.resolve_initial_references("RootPOA"));
            rootPOA.the_POAManager().activate();
            // Run the ORB
            final ORB orb = managedORB.object;
            new Thread() {

                @Override
                public void run() {
                    orb.run();
                }
            };
            System.out.println("ORB " + managedORB.config.getId() + " loaded");
            // Save the ORB
            saveORB(config.getId());

        } catch (Exception ex) {
            orbs.remove(config.getId());
            throw ex;
        }

    }

    public synchronized void destroyORB(String id) throws Exception {
    //TODO
    }

    public synchronized void saveORB(String id) throws Exception {
        if (!orbs.containsKey(id)) {
            throw new Exception("This ORB id does not exist.");
        }
        orbs.get(id).config.save();
    }

    public synchronized void createPOA(POAConfig config) throws Exception {
        try {
            if (idExists(config.getId())) {
                throw new Exception("This id already exists.");
            }
            // Put the orb in the orb list
            ManagedObject<POAConfig, POA> managedPOA = new ManagedObject<POAConfig, POA>(config);
            poas.put(config.getId(), managedPOA);
            // Load the ORB
            System.out.println("POA " + managedPOA.config.getId() + " loading");
            if (managedPOA.config.getId().equals("RootPOA")) {
                throw new Exception("RootPOA is a reserved name");
            }
            if (!poas.containsKey(managedPOA.config.getId())) {
                throw new Exception("This POA id does not exist.");
            }
            // Create the POA
            ORB orb = orbs.get(managedPOA.config.getIdORB()).object;
            POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
            managedPOA.object = rootPOA.create_POA(managedPOA.config.getId(), rootPOA.the_POAManager(), managedPOA.config.getPolicies(rootPOA));
            managedPOA.object.set_servant_manager(new GenericActivator()._this(orb));
            managedPOA.object.the_POAManager().activate();
            System.out.println("POA " + managedPOA.config.getId() + " loaded");
            // Save the ORB
            savePOA(config.getId());

        } catch (Exception ex) {
            poas.remove(config.getId());
            throw ex;
        }

    }

    public synchronized void destroyPOA(String id) throws Exception {
    //TODO
    }

    public synchronized void savePOA(String id) throws Exception {
        if (!poas.containsKey(id)) {
            throw new Exception("This POA id does not exist.");
        }
        poas.get(id).config.save();
    }

    public synchronized void createObject(ObjectConfig config) throws Exception {
        try {
            // TODO : begin
            System.out.println("Object " + config.getId() + " loading");
            if (idExists(config.getId())) throw new Exception("This id already exists.");
            if (config.getId().endsWith("_tmp") || config.getId().endsWith("_trx")) throw new Exception("This id is forbidden.");
            // Create the managed object
            ManagedObject<ObjectConfig, GenericObject> managedObject = new ManagedObject<ObjectConfig, GenericObject>(config);
            // Get object from config
            managedObject.object = managedObject.config.getValue();
            // Get object's managedPOA
            ManagedObject<POAConfig, POA> managedPOA = poas.get(managedObject.config.getIdPOA());
            // Get object's managedORB
            ManagedObject<ORBConfig, ORB> managedORB = orbs.get(managedPOA.config.getIdORB());
            // Get NamingContext
            NamingContextExt naming = NamingContextExtHelper.narrow(managedORB.object.resolve_initial_references("NameService"));
            if (!managedObject.config.isFirstLoad()) {
                // Reload the object
                objects.put(config.getId(), managedObject);
                ((PersistentObject)managedObject.object).reload(managedORB.object, managedPOA.object);
            } else {
                // Create and activate the servant
                managedPOA.object.activate_object_with_id(managedObject.config.getId().getBytes(), managedObject.config.getValue().createServant());
                // Get object' reference
                org.omg.CORBA.Object reference = poas.get(managedObject.config.getIdPOA()).object.id_to_reference(managedObject.config.getId().getBytes());
                // Bind the object in the name server
                if (managedObject.config.getBindingName() != null && !managedObject.config.getBindingName().equals("")) {
                    naming.rebind(naming.to_name(managedObject.config.getBindingName()), reference);
                }
                // Save ior file
                if (managedObject.config.getIorFileName() != null && !managedObject.config.getIorFileName().equals("")) {
                    PrintWriter ps = new PrintWriter(new FileOutputStream(new File(managedObject.config.getIorFileName())));
                    ps.print(managedORB.object.object_to_string(reference));
                    ps.close();
                }
                objects.put(config.getId(), managedObject);
                // Lauch the postCreate method
                managedObject.object.postCreate(managedORB.object, managedPOA.object);
                // Save the object
                if (managedObject.object instanceof PersistentObject) managedObject.config.save();
            }
            System.out.println("Object " + config.getId() + " loaded");
            // TODO : commit
        } catch (Exception ex) {
            // TODO : rollback
            objects.remove(config.getId());
            throw ex;
        }
    }

    public synchronized void destroyObject(String id) throws Exception {
        try {
            // TODO begin
            System.out.println("Object " + id + " destroying");
            if (!objects.containsKey(id)) throw new Exception("This object id does not exist.");
            ManagedObject<ObjectConfig, GenericObject> managedObject = objects.get(id);
            if (managedObject.object == null) throw new Exception("This Object is not loaded.");
            // Get object's managedPOA
            ManagedObject<POAConfig, POA> managedPOA = poas.get(managedObject.config.getIdPOA());
            // Get object's managedORB
            ManagedObject<ORBConfig, ORB> managedORB = orbs.get(managedPOA.config.getIdORB());
            // Predestroy the object
            managedObject.object.preDestroy(managedORB.object, managedPOA.object);
            NamingContextExt naming = NamingContextExtHelper.narrow(getObjectORB(id).resolve_initial_references("NameService"));
            if (managedObject.config.getIorFileName() != null && !managedObject.config.getIorFileName().equals("")) {
                new File(managedObject.config.getIorFileName()).delete();
            }
            if (managedObject.config.getBindingName() != null && !managedObject.config.getBindingName().equals("")) {
                naming.unbind(naming.to_name(managedObject.config.getBindingName()));
            }
            getObjectPOA(id).deactivate_object(managedObject.config.getId().getBytes());
            objects.remove(id);
            managedObject.config.destroy();
            System.out.println("Object " + id + " destroyed");
            // TODO : commit
        } catch (Exception ex) {
            // TODO : rollback
            throw ex;
        }

    }

    public synchronized void saveObject(String id) throws Exception {
        if (!objects.containsKey(id)) throw new Exception("This object id does not exist.");
        objects.get(id).config.save();   
    }

    public synchronized Servant recreateObject(String id) throws Exception {
        try {
            // TODO : begin
            System.out.println("Object " + id + " reloading");
            ManagedObject<ObjectConfig, GenericObject> managedObject = new ManagedObject<ObjectConfig, GenericObject>(new ObjectConfig(id));
            managedObject.object = managedObject.config.getValue();
            // Get object's managedPOA
            ManagedObject<POAConfig, POA> managedPOA = poas.get(managedObject.config.getIdPOA());
            // Get object's managedORB
            ManagedObject<ORBConfig, ORB> managedORB = orbs.get(managedPOA.config.getIdORB());
            //Reload the object
            ((PersistentObject)managedObject.object).reload(managedORB.object, managedPOA.object);
            objects.put(id, managedObject);
            System.out.println("Object " + id + " reloaded");
            // TODO : commit
            return managedObject.object.createServant();
        } catch (Exception ex) {
            // TODO : rollback
            throw ex;
        }
    }
    
    public synchronized void beginTransaction (String transastionId, String objectId) throws Exception {
        ObjectConfig.beginTransaction(transastionId, objectId);
    }
    
    public synchronized void rollbackTransaction (String transastionId, String objectId) throws Exception {
        ObjectConfig.rollbackTransaction(transastionId, objectId);
    }
    
    public synchronized void commitTransaction (String transastionId, String objectId) throws Exception {
        ObjectConfig.commitTransaction(transastionId, objectId);
    }

    public synchronized ORB getPOAORB(String id) throws Exception {
        return orbs.get(poas.get(id).config.getIdORB()).object;
    }

    public synchronized ORB getObjectORB(String id) throws Exception {
        return orbs.get(poas.get(objects.get(id).config.getIdPOA()).config.getIdORB()).object;
    }

    public synchronized POA getObjectPOA(String id) throws Exception {
        return poas.get(objects.get(id).config.getIdPOA()).object;
    }

    public synchronized Servant getObjectServant(String id) throws Exception {
        return getObjectPOA(id).id_to_servant(id.getBytes());
    }

    public synchronized org.omg.CORBA.Object getObjectReference(String id) throws Exception {
        return getObjectPOA(id).id_to_reference(id.getBytes());
    }
    
}
